//===============================================================================
// Microsoft patterns & practices
// Mobile Client Software Factory - July 2006
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom.Compiler;
using System.Reflection;
using System.IO;
using System.CodeDom;
using System.Configuration;
using System.Globalization;

namespace Microsoft.Practices.ObjectBuilder.ObGen
{
	static class Program
	{
		public static void Main(string[] args)
		{
			if (args.Length == 0)
			{
				Console.WriteLine(Properties.Resources.Usage);
				Console.ReadLine();
				return;
			}

			ObjectBuilderSection section;

			if (args[0].StartsWith("/config:"))
			{
				ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
				string fileName = StripQuotes(args[0]);
				fileName = fileName.Replace("/config:", "");
				fileName = StripQuotes(fileName);
				fileMap.ExeConfigFilename = fileName;
				Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
				section = (ObjectBuilderSection)config.GetSection(ObjectBuilderSection.SectionName);
				
				// Remove the config file
				string[] assemblyArgs = new string[args.Length - 1];
				Array.Copy(args, 1, assemblyArgs, 0, args.Length - 1);
				args = assemblyArgs;
			}
			else
			{
				section = ObjectBuilderSection.GetConfigurationSection();
			}

			if (section == null)
			{
				Console.WriteLine(String.Format(
					CultureInfo.CurrentCulture,
					Properties.Resources.NoConfiguration,
					ObjectBuilderSection.SectionName,
					typeof(ObjectBuilderSection)));
				return;				
			}

			if (args.Length == 0)
			{
				Console.WriteLine(Properties.Resources.Usage);
				return;
			}

			List<IPolicyGenerator> generators = new List<IPolicyGenerator>();
			foreach (PolicyGeneratorFactoryElement element in section.PolicyGenerators)
			{
				generators.Add(element.CreatePolicyGenerator());
			}

			CodeDomProvider codeProvider = (CodeDomProvider)Activator.CreateInstance(section.CodeProvider);
			AssemblyGenerator assemblyGenerator = new AssemblyGenerator(generators);

			foreach (string arg in args)
			{
				string assemblyFile = StripQuotes(arg);
				CodeCompileUnit unit = assemblyGenerator.AddAssembly(Assembly.LoadFrom(assemblyFile));

				if (section.EmitSources)
				{
					string fileName = Path.ChangeExtension(assemblyFile, "obgen." + codeProvider.FileExtension);
					using (StreamWriter writer = new StreamWriter(fileName, false))
					{
						CodeGeneratorOptions options = new CodeGeneratorOptions();
						options.BracingStyle = "C";

						codeProvider.GenerateCodeFromCompileUnit(unit, writer, options);
					}
				}

				CompilerParameters parameters = new CompilerParameters();
				parameters.IncludeDebugInformation = section.DebugSymbols;
				parameters.CompilerOptions = section.CompilerOptions;
				parameters.OutputAssembly = Path.ChangeExtension(assemblyFile, "obgen.dll");
				TransferReferences(parameters, unit);
				CompilerResults results = codeProvider.CompileAssemblyFromDom(parameters, unit);

				if (results.Errors.HasErrors)
				{
					foreach (CompilerError error in results.Errors)
					{
						Console.WriteLine(error);
					}
					foreach (string message in results.Output)
					{
						Console.WriteLine(message);
					}
				}
			}

			Console.WriteLine(Properties.Resources.Done);
		}

		private static void TransferReferences(CompilerParameters parameters, CodeCompileUnit compileUnit)
		{
			foreach (string assemblyRef in compileUnit.ReferencedAssemblies)
			{
				if (!ContainsReference(parameters.ReferencedAssemblies, assemblyRef))
				{
					parameters.ReferencedAssemblies.Add(assemblyRef);
				}
			}

			compileUnit.ReferencedAssemblies.Clear();
		}

		/// <devdoc>
		/// Does a file name case insensitive comparison of all references to find a match. It's version neutral because 
		/// only one version of a given assembly can be passed-in to the CodeDomProvider for compilation.
		/// </devdoc>
		private static bool ContainsReference(System.Collections.Specialized.StringCollection references, string assemblyFile)
		{
			string simpleFileName = Path.GetFileName(assemblyFile);

			foreach (string file in references)
			{
				if (Path.GetFileName(file).Equals(simpleFileName, StringComparison.CurrentCultureIgnoreCase))
				{
					return true;
				}
			}

			return false;
		}

		private static string StripQuotes(string fileName)
		{
			if (fileName.StartsWith("\"")) fileName = fileName.Substring(1);
			if (fileName.EndsWith("\"")) fileName = fileName.Substring(0, fileName.Length - 1);
			return fileName;
		}
	}
}
